﻿using System;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
using System.ServiceModel;
using System.Timers;
using Sern.Abstracts.Common;
using Sern.Abstracts.Tracker.DataContracts;
using Sern.Abstracts.Tracker.ServiceContracts;
using Sern.Common;
using Microsoft.Practices.Unity;

namespace Sern.ServiceImplementation.MapReduce
{
    public abstract class MapReduceClientEnvelope<TMr, TKey,TVal> : MarshalByRefObject, IClientInstance
        where TMr : class, IMapReduce, IMapReduce<TKey,TVal>
    {        
        private Listener _mrListener;
        private TMr _mrInstance;
        private Timer _aliveNotificationTimer = new Timer(Constant.ClientKeepAliveRate.TotalMilliseconds * 0.67);
        private IDisposable _removeKeepAliveNotification;

        private Uri BaseUri
        {
            get
            {
                var bindingScheme = ComponentLocator.Unity.Resolve<IBindingFactory>()[
                    ComponentLocator.Unity.Resolve<IClientTypeToEnumMapper>()[
                        typeof (TMr)
                    ]
                ]();
                Uri baseUri =
                    new UriBuilder(bindingScheme.Scheme, ComponentLocator.Unity.Resolve<string>("ClientHostName"), bindingScheme.Port ?? -1,
                                   null).Uri;

                return baseUri;
            }
        }

        
        private Uri RelativeUri
        {
            get
            {
                return new Uri(ComponentLocator.Unity.Resolve<IClientTypeToEnumMapper>()[typeof(TMr)] + "/" + ComponentLocator.Unity.Resolve<Guid>("ClientId").ToString("N"), UriKind.Relative);
            }
        }

        protected abstract void Handle(TKey key,TVal value, Action<object,object> nextCallback);

        protected abstract void End();


        void IClientInstance.Initialize(ClientInitializationArgument arg)
        {           
            ComponentLocator.Unity.RegisterInstance("ClientJobId", arg.JobId);
            ComponentLocator.Unity.RegisterInstance("ClientId", arg.ClientId);
            ComponentLocator.Unity.RegisterInstance("ClientLogLevel", arg.LogLevel);
            ComponentLocator.Unity.RegisterInstance("ClientHostName", arg.HostName);
            ComponentLocator.Unity.RegisterInstance("ClientTrackerUri", arg.TrackerUri);            
            ComponentLocator.Unity.RegisterInstance("ClientMapReduceHostUri", arg.MapReduceHostUri);
            ComponentLocator.Unity.RegisterInstance<Action<TKey, TVal, Action<object, object>>>("ClientMapReduceDelegate",Handle);
            ComponentLocator.Unity.RegisterInstance<Action>("ClientEndDelegate", End);

        }




        void IClientInstance.Register()
        {
            //TMR will be registered via the local registrar.
            _mrInstance = ComponentLocator.Unity.Resolve<TMr>();
            var contractType = typeof (TMr).GetInterfaces().Single(i => typeof (IMapReduce).IsAssignableFrom(i) && !i.Name.StartsWith("IMapReduce"));
            var clientTypeEnum = ComponentLocator.Unity.Resolve<IClientTypeToEnumMapper>()[contractType];

            //Starts the map reduce listener.
            _mrListener = ComponentLocator.Unity.Resolve<IListenerFactory>()
                .Create(BaseUri, RelativeUri, clientTypeEnum, _mrInstance);           
            _mrListener.Open();
            _removeKeepAliveNotification = SwitchOnKeepAliveNotification();
            
            //Register the new MR client instance to the tracker!
            using(IClient<ITracker> trackerProxy = ComponentLocator.Unity.Resolve<Func<IClient<ITracker>>>()())
            {                
                trackerProxy.Proxy.Subscribe(ComponentLocator.Unity.Resolve<Guid>("ClientJobId"), ComponentLocator.Unity.Resolve<Guid>("ClientId"),
                    ComponentLocator.Unity.Resolve<IClientTypeToEnumMapper>()[typeof(TMr)],
                    new Uri(BaseUri,RelativeUri).AbsoluteUri,
                    typeof(TKey).GetGenericArguments()[0].FullName,
                    typeof(TVal).GetGenericArguments()[1].FullName,
                    ComponentLocator.Unity.Resolve<string>("ClientMapReduceHostUri"));
            }            
        }

        private IDisposable SwitchOnKeepAliveNotification()
        {
            return Observable.FromEventPattern<ElapsedEventHandler,ElapsedEventArgs>(
                _ => _aliveNotificationTimer.Elapsed += _,
                _ => _aliveNotificationTimer.Elapsed -= _
                ).Subscribe(
                    Observer.Create<EventPattern<ElapsedEventArgs>>
                        (_ =>
                            {
                                using(IClient<ITrackerTelemetryReceiver> trackerTelemetryProxy = ComponentLocator.Unity.Resolve<Func<IClient<ITrackerTelemetryReceiver>>>()())
                                {
                                    trackerTelemetryProxy.Proxy.Notify(
                                        new IsAliveNotice
                                            {
                                                ClientId = ComponentLocator.Unity.Resolve<Guid>("ClientId")
                                            });
                                }
                            }));
        }

        void IClientInstance.Deregister()
        {
            //Deregister from the tracker.
            using (var tracker = ComponentLocator.Unity.Resolve<Func<IClient<ITracker>>>()())
            {
                tracker.Proxy.Unsubscribe(ComponentLocator.Unity.Resolve<Guid>("ClientId"));
            }

            _removeKeepAliveNotification.Dispose();

            //Close the listener
            _mrListener.Close();           

            //Sets the map reduce instance to null so the object may be garbage collected.
            _mrInstance = null;

        }
    }
}
